using Pegasus.Diagnostics;
using System;
using System.Collections;
using System.Threading;

using Pegasus.Log4Net;

namespace Pegasus.Threading
{
	/// <summary>
	/// This class is used to process requests or object on another thread.  The user can queue up
	/// as many object as needed and they will be process on a background thread.
	/// </summary>
	public class ProcessQueue
	{
		// Local Instance Value
		private ILog m_log = LogManager.GetLogger( typeof( ProcessQueue ) );

		/// <summary></summary>
		protected WaitCallback m_processHandler = null;
		
		/// <summary></summary>
		protected Queue m_objectQueue = Queue.Synchronized( new Queue() );
		
		/// <summary></summary>
		protected AutoResetEvent m_newObjEvent = new AutoResetEvent( false );

		/// <summary></summary>
		protected bool m_running = false;

		/// <summary>
		/// Default Constructor
		/// </summary>
		public ProcessQueue()
		{
		}

		/// <summary>
		/// Gets the number of objects in the queue
		/// </summary>
		public int Count
		{
			get
			{
				return m_objectQueue.Count;
			}
		}

		/// <summary>
		/// Returns true if the queue is running and false if it is not.
		/// </summary>
		public bool Running
		{
			get
			{
				return m_running;
			}
		}

		/// <summary>
		/// The system will call the given handler when a object needs to be process from the
		/// queue.  The call will be on a background thread.
		/// </summary>
		/// <param name="processHandler">Handler to process the requested object</param>
		public void RegisterCallback( WaitCallback processHandler )
		{
			if( m_running )
			{
				throw new ArgumentException( "Can not change handers while queue is running" );
			}

			m_processHandler = processHandler;
		}

		/// <summary>
		/// Start process object in the queue
		/// </summary>
		public void Start()
		{
			if( m_processHandler == null )
			{
				throw new ArgumentException( "No callback method registered" );
			}

			m_running = true;

			ThreadStart threadStart = new ThreadStart( ThreadProc );
			Thread thread = new Thread( threadStart );
			thread.IsBackground = true;
			thread.Name = "ProcessQueue " + GetHashCode();

			thread.Start();
		}

		/// <summary>
		/// Stop processing objects in the queue.  This will block until there are no
		/// more requests or objects in the queue left to be processed.
		/// </summary>
		public void Stop()
		{
			Stop( true );
		}

		/// <summary>
		/// Stop processing objects in the queue.  The "wait" argument indicates
		/// whether or not we should wait for the queue to be empty before 
		/// we stop processing.
		/// </summary>
		/// <param name="wait"></param>
		public virtual void Stop( bool wait )
		{
			if( wait )
			{
				while( m_objectQueue.Count > 0 )
				{
					// just loop and yeld thread
					Thread.Sleep( 1 );
				}
			}

			m_running = false;
			m_newObjEvent.Set();
		}

		/// <summary>
		/// Add the given object to the queue
		/// </summary>
		/// <param name="obj">The object to add to the queue</param>
		public void Enqueue( object obj )
		{
			// Lock the queue while adding an object.
			lock( m_objectQueue.SyncRoot )
			{
				m_objectQueue.Enqueue( obj );
				m_newObjEvent.Set();
			}
		}

		/// <summary>
		/// The background thread for processing the objects in the queue.
		/// </summary>
		protected virtual void ThreadProc()
		{
			while( m_running )
			{
				object obj = null;

				// Lock the queue while we get the next object
				lock( m_objectQueue.SyncRoot )
				{
					// Get the object from the queue.  If there are no object in the queue
					// and we call the Dequeue() method it will throw.
					if( m_objectQueue.Count > 0 )
					{
						obj = m_objectQueue.Dequeue();
						m_newObjEvent.Reset();
					}
				}

				if( obj != null )
				{
					try
					{
						m_processHandler( obj );
					}
					catch( Exception e )
					{
						m_log.Error( "Unhandled exception while processing object in ProcessQueue class", e );
					}
				}
				else
				{
					m_newObjEvent.WaitOne();
				}
			}
		}
	}
}
